﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using CommandLine;
using Microsoft.CSharp;
using Microsoft.JScript;
using Microsoft.VisualBasic;

namespace ChainMath.Generator
{
    internal static class Program
    {
        private static ChainMathGeneratorOptions ParseOptionss(string[] args)
        {
            var parser = new CommandLineParser(new CommandLineParserSettings(true, false, false, Console.Error));
            var options = new ChainMathGeneratorOptions();

            return parser.ParseArguments(args, options) ? options : null;
        }

        private static CodeParameterDeclarationExpression CreateParameter(ParameterInfo parameterInfo, bool _this)
        {
            var parameterType = parameterInfo.ParameterType ;
            parameterType = parameterType.IsByRef ? parameterType.GetElementType() : parameterType;

            // TODO: fix
            // i think it will be work fine only in CS
            var typeRef = new CodeTypeReference(_this ? "this " + parameterType.FullName : parameterType.FullName);

            return new CodeParameterDeclarationExpression()
                    {
                        Type = typeRef,
                        Name = parameterInfo.Name,
                        Direction = parameterInfo.IsOut ? FieldDirection.Out :
                            parameterInfo.IsRetval ? FieldDirection.Ref :
                            FieldDirection.In
                    };
        }

        private static CodeTypeDeclaration CreateExtensionClass()
        {
            var math = typeof (Math);
            var methods = math.GetMethods(BindingFlags.Static | BindingFlags.Public);

            var extensionClass = new CodeTypeDeclaration("ChainMathExt");
                                     
            // TODO: Dirty, fix it!
            extensionClass.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, "extclass\nstatic"));


            var mathTypeReference = new CodeTypeReferenceExpression(math);
            var extensionAttrReference = new CodeTypeReference(typeof(ExtensionAttribute));
            var extensionAttrDeclaration = new CodeAttributeDeclaration(extensionAttrReference);

            foreach (var method in methods)
            {
                var memberMethod = new CodeMemberMethod
                                       {
                                           Name = method.Name,
                                           Attributes = MemberAttributes.Static | MemberAttributes.Public,
                                           ReturnType = new CodeTypeReference(method.ReturnParameter.ParameterType)
                                       };

                var parameters = method.GetParameters()
                    .Select((p, i) => CreateParameter(p, i == 0))
                    .ToArray();

                var invocationArguments = parameters
                    .Select(p => new CodeDirectionExpression(p.Direction, new CodeArgumentReferenceExpression(p.Name)))
                    .Cast<CodeExpression>() /* to avoid CoVariantArrayConversion */
                    .ToArray();

                var originalMethod = new CodeMethodReferenceExpression(mathTypeReference, method.Name);
                var originalInvocationStatement = new CodeMethodInvokeExpression(originalMethod, invocationArguments);

                memberMethod.Parameters.AddRange(parameters);

                memberMethod.Statements.Add(new CodeMethodReturnStatement(originalInvocationStatement));

                //memberMethod.CustomAttributes.Add(extensionAttrDeclaration);
                extensionClass.Members.Add(memberMethod);
            }

            // TODO: fix
            extensionClass.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, String.Empty));

            return extensionClass;
        }

        private static CodeDomProvider CreateCodeDomProvider(ChainMathGeneratorOptions.CMTargetLanguage targetLanguage)
        {
            switch (targetLanguage)
            {
                case ChainMathGeneratorOptions.CMTargetLanguage.Cs:
                    return new CSharpCodeProvider();

                case ChainMathGeneratorOptions.CMTargetLanguage.Vb:
                    return new VBCodeProvider();

                case ChainMathGeneratorOptions.CMTargetLanguage.JScript:
                    return new JScriptCodeProvider();

                default:
                    throw new NotSupportedException();

            }
        }

        static int Main(string[] args)
        {
            var options = ParseOptionss(args);
            if (options == null) return 1;

            if (options.Namespace == null && !options.GlobalNamespace)
            {
                Console.Error.WriteLine("Namesapace must be specified or explicitly set to global");
                Console.Error.WriteLine("Use '--help' for details");

                return 1;
            }

            var comilationUnit = new CodeCompileUnit();
            var extClass = CreateExtensionClass();

            var ns = new CodeNamespace(options.GlobalNamespace ? null : options.Namespace);
            ns.Types.Add(extClass);
            comilationUnit.Namespaces.Add(ns);

            var output = options.OutputFile == null ? Console.Out : File.CreateText(options.OutputFile);


            var codeDomProvider = CreateCodeDomProvider(options.TargetLanguage);
            codeDomProvider.GenerateCodeFromCompileUnit(comilationUnit, output, new CodeGeneratorOptions());

            output.Flush();

           

            return 0;
        }
    }
}
